﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Web.Script.Serialization;
using SimpleSpeedTester.Core;
using SimpleSpeedTester.Core.OutcomeFilters;
using SimpleSpeedTester.Interfaces;
using JsonNetJsonConvert = Newtonsoft.Json.JsonConvert;
using JayrockJsonConvert = Jayrock.Json.Conversion.JsonConvert;

namespace SimlpeSpeedTester.Example
{
    /// <summary>
    /// Demo program which compares the serializatoin and deserialization speed of 4 popular JSON serializers
    /// </summary>
    public static class JsonSerializersSpeedTest
    {
        // test serialization and deserialization on 100k objects
        private const int ObjectsCount = 100000;

        // perform 5 runs of each test
        private const int TestRuns = 5;

        private static readonly Random RandomGenerator = new Random(DateTime.UtcNow.Millisecond);

        // exclude the min and max results 
        private static readonly ITestOutcomeFilter OutcomeFilter = new ExcludeMinAndMaxTestOutcomeFilter();

        // the objects to perform the tests with
        private static readonly List<SimpleObject> SimpleObjects = Enumerable.Range(1, ObjectsCount).Select(GetSimpleObject).ToList();

        public static void Start()
        {
            // speed test Json.Net serializer
            DoSpeedTest("Json.Net", SerializeWithJsonNet, DeserializeWithJsonNet<SimpleObject>);

            // speed test ServiceStack.Text Json serializer
            DoSpeedTest("ServiceStack.Text", SerializeWithServiceStack, DeserializeWithServiceStack<SimpleObject>);

            // speed test DataContractJsonSerializer
            DoSpeedTest("DataContractJsonSerializer", SerializeWithDataContractJsonSerializer, DeserializeWithDataContractJsonSerializer<SimpleObject>);

            // speed test JavaScriptSerializer
            DoSpeedTest("JavaScriptSerializer", SerializeWithJavaScriptSerializer, DeserializeWithJavaScriptSerializer<SimpleObject>);

            // speed test SimpleJson
            DoSpeedTest("SimpleJson", SerializeWithSimpleJson, DeserializeWithSimpleJson<SimpleObject>);

            // speed test fastJson
            DoSpeedTest("fastJson", SerializeWithFastJson, DeserializeWithFastJson<SimpleObject>);

            // speed test JayRock
            DoSpeedTest("JayRock", SerializeWithJayRock, DeserializeWithJayRock<SimpleObject>);
        }

        private static void DoSpeedTest(
            string testGroupName, Func<List<SimpleObject>, List<string>> serializeFunc, Func<List<string>, List<SimpleObject>> deserializeFunc)
        {
            var jsonStrings = new List<string>();

            var testGroup = new TestGroup(testGroupName);

            var serializationTestSummary =
                testGroup
                    .Plan("Serialization", () => jsonStrings = serializeFunc(SimpleObjects), TestRuns)
                    .GetResult()
                    .GetSummary(OutcomeFilter);

            Console.WriteLine(serializationTestSummary);

            var deserializationTestSummary =
                testGroup
                    .Plan("Deserialization", () => deserializeFunc(jsonStrings), TestRuns)
                    .GetResult()
                    .GetSummary(OutcomeFilter);

            Console.WriteLine(deserializationTestSummary);
        }

        private static SimpleObject GetSimpleObject(int id)
        {
            return new SimpleObject
            {
                Name = string.Format("Simple-{0}", id),
                Id = RandomGenerator.Next(1, ObjectsCount),
                Address = "Planet Earth",
                Scores = Enumerable.Range(0, 10).Select(i => RandomGenerator.Next(1, 100)).ToArray()
            };
        }

        #region DataContractJsonSerializer

        private static List<string> SerializeWithDataContractJsonSerializer<T>(List<T> objects)
        {
            var serializer = new DataContractJsonSerializer(typeof(T));
            var jsonStrings = objects.Select(
                o =>
                {
                    using (var memStream = new MemoryStream())
                    {
                        serializer.WriteObject(memStream, o);
                        return Encoding.Default.GetString(memStream.ToArray());
                    }
                }).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithDataContractJsonSerializer<T>(List<string> jsonStrings)
        {
            var serializer = new DataContractJsonSerializer(typeof(T));
            var objects = jsonStrings.Select(
                str =>
                {
                    using (var memStream = new MemoryStream(Encoding.Default.GetBytes(str)))
                    {
                        return (T)serializer.ReadObject(memStream);
                    }
                }).ToList();
            return objects;
        }

        #endregion

        #region JavaScriptSerializer

        private static List<string> SerializeWithJavaScriptSerializer<T>(List<T> objects)
        {
            var serializer = new JavaScriptSerializer();
            var jsonStrings = objects.Select(o => serializer.Serialize(o)).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithJavaScriptSerializer<T>(List<string> jsonStrings)
        {
            var serializer = new JavaScriptSerializer();
            var objects = jsonStrings.Select(serializer.Deserialize<T>).ToList();
            return objects;
        }

        #endregion

        #region Json.Net

        private static List<string> SerializeWithJsonNet<T>(List<T> objects)
        {
            var jsonStrings = objects.Select(o => JsonNetJsonConvert.SerializeObject(o)).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithJsonNet<T>(List<string> jsonStrings)
        {
            var objects = jsonStrings.Select(JsonNetJsonConvert.DeserializeObject<T>).ToList();
            return objects;
        }

        #endregion

        #region ServiceStack

        private static List<string> SerializeWithServiceStack<T>(List<T> objects)
        {
            var serializer = new ServiceStack.Text.JsonSerializer<T>();

            var jsonStrings = objects.Select(serializer.SerializeToString).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithServiceStack<T>(List<string> jsonStrings)
        {
            var serializer = new ServiceStack.Text.JsonSerializer<T>();

            var objects = jsonStrings.Select(serializer.DeserializeFromString).ToList();
            return objects;
        }

        #endregion

        #region SimpleJson

        private static List<string> SerializeWithSimpleJson<T>(List<T> objects)
        {
            var jsonStrings = objects.Select(o => SimpleJson.SimpleJson.SerializeObject(o)).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithSimpleJson<T>(List<string> jsonStrings)
        {
            var objects = jsonStrings.Select(SimpleJson.SimpleJson.DeserializeObject<T>).ToList();
            return objects;
        }

        #endregion

        #region FastJson

        private static List<string> SerializeWithFastJson<T>(List<T> objects) {
            var jsonStrings = objects.Select(o => fastJSON.JSON.Instance.ToJSON(o)).ToList();
            return jsonStrings;
        }

        private static List<T> DeserializeWithFastJson<T>(List<string> jsonStrings) {
            var objects = jsonStrings.Select(fastJSON.JSON.Instance.ToObject<T>).ToList();
            return objects;
        }

        #endregion

        #region JayRock

        private static List<string> SerializeWithJayRock<T>(List<T> objects) {
            var jsonStrings = 
                objects.Select(o => JayrockJsonConvert.ExportToString(o)).ToList();
                
            return jsonStrings;
        }

        private static List<T> DeserializeWithJayRock<T>(List<string> jsonStrings) {
            var objects = jsonStrings.Select(JayrockJsonConvert.Import<T>).ToList();
            return objects;
        }

        #endregion

        #region SimpleObject

        [DataContract]
        public class SimpleObject
        {
            [DataMember]
            public int Id { get; set; }

            [DataMember]
            public string Name { get; set; }

            [DataMember]
            public string Address { get; set; }

            [DataMember]
            public int[] Scores { get; set; }
        }

        #endregion
    }
}